package sg.simple.snmp.util;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.log4j.Logger;
import org.taksmind.subnet.Subnet;

import sg.simple.snmp.provider.IpDiscoveryCallback;
import sg.simple.snmp.provider.NetMapException;

public class NetworkUtils {

	private static NetworkUtils instance = new NetworkUtils();

	private Logger logger = Logger.getLogger(NetworkUtils.class);

	private static ExecutorService threadPool = Executors.newCachedThreadPool();

	private NetworkUtils() {

	}

	public static NetworkUtils instance() {
		return instance;
	}

	public void discoverActiveIpsAsync(final String subnet,
			final int timeoutInMillies, final IpDiscoveryCallback callback)
			throws NetMapException {

		Runnable runnable = new Runnable() {

			@Override
			public void run() {
				try {
					discoverActiveIps(subnet, timeoutInMillies, callback);
				} catch (NetMapException ex) {
					logger.error(ex);
				}

			}
		};

		threadPool.execute(runnable);
	}

	private List<String> discoverActiveIps(String subnet, int timeoutInMillies,
			IpDiscoveryCallback callback) throws NetMapException {
		List<String> reachableIps = new ArrayList<String>();
		try {

			Subnet network = new Subnet();
			InetAddress localHost = Inet4Address.getLocalHost();
			network.setIPAddress(localHost.getHostAddress());
			network.setSubnetMask(subnet);

			String min = network.getMinimumHostAddressRange();
			String max = network.getMaximumHostAddressRange();
			logger.info("manimum ip : " + min);
			logger.info("maximum : " + max);
			logger.info("number of usable ips " + network.getUsableHosts());
			logger.info("number of total ips " + network.getTotalHosts());

			List<String> fullIpRange = getIpsWithInRange(min, max);

			for (String ip : fullIpRange) {

				if (InetAddress.getByName(ip).isReachable(timeoutInMillies)) {
					logger.info(ip + "is reachable");
					reachableIps.add(ip);

					if (callback != null) {
						callback.onActiveIpDiscovered(ip);
					}

				} else {
					logger.info(ip + "is unreachable");
				}
			}

		} catch (Exception ex) {

			NetMapException netmapEx = new NetMapException(
					"unable to check for hosts in subnet: " + subnet, ex);
			logger.error(ex);
			if (callback != null) {
				callback.onError(netmapEx);
			} else {
				throw netmapEx;
			}

		}

		return reachableIps;
	}

	public List<String> discoverActiveIpsSync(String subnet,
			int timeoutInMillies) throws NetMapException {
		return this.discoverActiveIps(subnet, timeoutInMillies, null);
	}

	public List<String> getIpsWithInRange(String min, String max) {
		List<String> ipRange = new ArrayList<String>();

		String[] ipFragmentsMin = min.replace('.', 'a').split("a");
		assert (ipFragmentsMin.length == 4);

		String[] ipFragmentsMax = max.replace('.', 'a').split("a");
		assert (ipFragmentsMax.length == 4);

		int[] minNumbers = convert(ipFragmentsMin);
		int[] maxNumbers = convert(ipFragmentsMax);

		int result[] = new int[minNumbers.length];

		for (int i = 0; i < result.length; i++) {
			result[i] = maxNumbers[i] - minNumbers[i];
		}

		for (int first = 0; first <= result[0]; first++) {

			for (int second = 0; second <= result[1]; second++) {

				for (int thrid = 0; thrid <= result[2]; thrid++) {

					for (int fourth = 0; fourth <= result[3]; fourth++) {

						String ip = String.format("%d.%d.%d.%d", minNumbers[0]
								+ first, minNumbers[1] + second, minNumbers[2]
								+ thrid, minNumbers[3] + fourth);
						ipRange.add(ip);
					}

				}

			}

		}

		return ipRange;
	}

	private int[] convert(String[] fragments) {

		int[] intFragments = new int[fragments.length];

		for (int i = 0; i < fragments.length; i++) {

			intFragments[i] = Integer.parseInt(fragments[i]);
		}

		return intFragments;
	}

}
